import { _decorator, Component, RigidBody2D, Vec2, input, Input, KeyCode, PhysicsSystem2D, math, game, director, Director, Game, EventKeyboard } from 'cc';
import { PhysicalSystemDetection } from './PhysicalSystemDetection';
import { PlayerDataWithDash } from './PlayerDataWithDash';
const { ccclass, property, menu } = _decorator;

@ccclass('PlayerMovementWithDash')
export class PlayerMovementWithDash extends Component {
    @property(PlayerDataWithDash)
    data: PlayerDataWithDash = null;

    @property(RigidBody2D)
    rb: RigidBody2D = null;

    @property({ type: PhysicalSystemDetection })
    groundCheck: PhysicalSystemDetection = null;

    @property({ type: PhysicalSystemDetection })
    rightWallCheck: PhysicalSystemDetection = null;

    @property({ type: PhysicalSystemDetection })
    leftWallCheck: PhysicalSystemDetection = null;

    // State parameters
    isFacingRight: boolean = true;
    isJumping: boolean = false;
    isWallJumping: boolean = false;
    isDashing: boolean = false;
    isSliding: boolean = false;

    // Timers
    lastOnGroundTime: number = 0;
    lastOnWallTime: number = 0;
    lastOnWallRightTime: number = 0;
    lastOnWallLeftTime: number = 0;
    lastPressedJumpTime: number = 0;
    lastPressedDashTime: number = 0;

    // Jump control
    private _isJumpCut: boolean = false;
    private _isJumpFalling: boolean = false;

    // Wall jump control
    private _wallJumpStartTime: number = 0;
    private _lastWallJumpDir: number = 0;

    // Dash control
    private _dashesLeft: number = 1;
    private _dashRefilling: boolean = false;
    private _lastDashDir: Vec2 = Vec2.ZERO;
    private _isDashAttacking: boolean = false;

    // Input
    private _moveInput: Vec2 = new Vec2();
    private keyState: Map<KeyCode, boolean> = new Map();

    onLoad() {
        if (!this.rb) {
            this.rb = this.getComponent(RigidBody2D);
        }

        this.setGravityScale(this.data.gravityScale);
        this.isFacingRight = true;
        this._dashesLeft = this.data.dashAmount;

        // Setup input
        input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
        input.on(Input.EventType.KEY_UP, this.onKeyUp, this)
    }

    onDestroy() {
        input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
        input.off(Input.EventType.KEY_UP, this.onKeyUp, this);
    }

    update(deltaTime: number) {
        // Update timers
        this.lastOnGroundTime -= deltaTime;
        this.lastOnWallTime -= deltaTime;
        this.lastOnWallRightTime -= deltaTime;
        this.lastOnWallLeftTime -= deltaTime;
        this.lastPressedJumpTime -= deltaTime;
        this.lastPressedDashTime -= deltaTime;

        // Handle input
        this.handleInput();

        // Collision checks
        if (!this.isDashing && !this.isJumping) {
            this.checkCollisions();
        }

        // Jump checks
        this.checkJumpState();

        // Dash checks
        if (this.canDash() && this.lastPressedDashTime > 0) {
            this.startDash();
        }

        // Slide checks
        this.checkSlideState();

        // Gravity
        this.applyGravity();
    }

    lateUpdate(deltaTime: number) {
        // Handle run
        if (!this.isDashing) {
            if (this.isWallJumping) {
                this.run(this.data.wallJumpRunLerp);
            } else {
                this.run(1);
            }
        } else if (this._isDashAttacking) {
            this.run(this.data.dashEndRunLerp);
        }

        // Handle slide
        if (this.isSliding) {
            this.slide();
        }
    }

    // Input handling
    private handleInput() {
        this._moveInput.set(0, 0);
        if (this.keyState.get(KeyCode.KEY_A) || this.keyState.get(KeyCode.ARROW_LEFT)) {
            this._moveInput.x -= 1;
        }
        if (this.keyState.get(KeyCode.KEY_D) || this.keyState.get(KeyCode.ARROW_RIGHT)) {
            this._moveInput.x += 1;
        }
        if (this.keyState.get(KeyCode.KEY_W) || this.keyState.get(KeyCode.ARROW_UP)) {
            this._moveInput.y += 1;
        }
        if (this.keyState.get(KeyCode.KEY_S) || this.keyState.get(KeyCode.ARROW_DOWN)) {
            this._moveInput.y -= 1;
        }

        if (this._moveInput.x !== 0) {
            this.checkDirectionToFace(this._moveInput.x > 0);
        }
    }

    private onKeyDown(event: EventKeyboard) {
        this.keyState.set(event.keyCode, true);
        switch (event.keyCode) {
            case KeyCode.SPACE:
            case KeyCode.KEY_C:
            case KeyCode.KEY_J:
                this.onJumpInput();
                break;
            case KeyCode.KEY_X:
            case KeyCode.SHIFT_LEFT:
            case KeyCode.KEY_K:
                this.onDashInput();
                break;
        }
    }

    private onKeyUp(event: any) {
        this.keyState.set(event.keyCode, false);
        switch (event.keyCode) {
            case KeyCode.SPACE:
            case KeyCode.KEY_C:
            case KeyCode.KEY_J:
                this.onJumpUpInput();
                break;
        }
    }

    // Collision detection
    private checkCollisions() {
        if (this.groundCheck && !this.isJumping && (this.groundCheck.contacts.length > 0)) {
            this.lastOnGroundTime = this.data.coyoteTime;
        }

        const rightWall = this.rightWallCheck && this.rightWallCheck.contacts.length > 0;
        const leftWall = this.leftWallCheck && this.leftWallCheck.contacts.length > 0;

        if (rightWall && this.isFacingRight) {
            this.lastOnWallRightTime = this.data.coyoteTime;
        }

        if (leftWall && !this.isFacingRight) {
            this.lastOnWallLeftTime = this.data.coyoteTime;
        }

        this.lastOnWallTime = Math.max(this.lastOnWallLeftTime, this.lastOnWallRightTime);
    }

    // Movement methods
    private run(lerpAmount: number) {
        //目标速度
        let targetSpeed = this._moveInput.x * this.data.runMaxSpeed;
        targetSpeed = Vec2.lerp(new Vec2(), new Vec2(this.rb.linearVelocity.x, 0), new Vec2(targetSpeed, 0), lerpAmount).x;

        //是否在地面
        let accelRate: number;
        if (this.lastOnGroundTime > 0) {
            accelRate = (Math.abs(targetSpeed) > 0.01) ? this.data.runAccelAmount : this.data.runDeccelAmount;
        } else {
            accelRate = (Math.abs(targetSpeed) > 0.01) ?
                this.data.runAccelAmount * this.data.accelInAir :
                this.data.runDeccelAmount * this.data.deccelInAir;
        }

        //悬停优化
        if ((this.isJumping || this.isWallJumping || this._isJumpFalling) &&
            Math.abs(this.rb.linearVelocity.y) < this.data.jumpHangTimeThreshold) {
            accelRate *= this.data.jumpHangAccelerationMult;
            targetSpeed *= this.data.jumpHangMaxSpeedMult;
        }
        //保持动量
        if (this.data.doConserveMomentum &&
            Math.abs(this.rb.linearVelocity.x) > Math.abs(targetSpeed) &&
            Math.sign(this.rb.linearVelocity.x) === Math.sign(targetSpeed) &&
            Math.abs(targetSpeed) > 0.01 &&
            this.lastOnGroundTime < 0) {
            accelRate = 0;// 禁用加速度
        }
        //最终力的计算
        const speedDif = targetSpeed - this.rb.linearVelocity.x;
        const movement = speedDif * accelRate * PhysicsSystem2D.instance.fixedTimeStep;
        this.rb.applyForceToCenter(new Vec2(movement, 0), true);
    }

    private turn() {
        this.node.scale.multiply3f(-1, 1, 1);
        this.isFacingRight = !this.isFacingRight;
    }

    // Jump methods
    private checkJumpState() {
        // 跳跃下落检测
        if (this.isJumping && this.rb.linearVelocity.y < 0) {
            this.isJumping = false;
            if (!this.isWallJumping) {//不是墙跳
                this._isJumpFalling = true;
            }
        }
        //墙跳持续时间控制
        if (this.isWallJumping && Date.now() - this._wallJumpStartTime > this.data.wallJumpTime * 1000) {
            this.isWallJumping = false;
        }
        //地面状态重置
        if (this.lastOnGroundTime > 0 && !this.isJumping && !this.isWallJumping) {
            this._isJumpCut = false;
            if (!this.isJumping) {
                this._isJumpFalling = false;
            }
        }
        //跳跃执行判断
        if (!this.isDashing) {
            if (this.canJump() && this.lastPressedJumpTime > 0) {
                this.jump();
            } else if (this.canWallJump() && this.lastPressedJumpTime > 0) {
                this.wallJump();
            }
        }
    }

    private jump() {
        this.isJumping = true;
        this.isWallJumping = false;
        this._isJumpCut = false;
        this._isJumpFalling = false;

        this.lastPressedJumpTime = 0;
        this.lastOnGroundTime = 0;

        let force = this.data.jumpForce;

        if (this.rb.linearVelocity.y < 0) {
            force -= this.rb.linearVelocity.y;
        }
        this.rb.applyForceToCenter(new Vec2(0, force), true);
    }

    private wallJump() {
        this.isWallJumping = true;
        this.isJumping = false;
        this._isJumpCut = false;
        this._isJumpFalling = false;

        this._wallJumpStartTime = Date.now();
        this._lastWallJumpDir = (this.lastOnWallRightTime > 0) ? -1 : 1;

        this.lastPressedJumpTime = 0;
        this.lastOnGroundTime = 0;
        this.lastOnWallRightTime = 0;
        this.lastOnWallLeftTime = 0;

        const force = new Vec2(
            this.data.wallJumpForce.x * this._lastWallJumpDir,
            this.data.wallJumpForce.y
        );

        if (Math.sign(this.rb.linearVelocity.x) !== Math.sign(force.x)) {
            force.x -= this.rb.linearVelocity.x;
        }

        if (this.rb.linearVelocity.y < 0) {
            force.y -= this.rb.linearVelocity.y;
        }

        this.rb.applyForceToCenter(force, true);
    }

    // Dash methods
    private startDash() {
        this.sleep(this.data.dashSleepTime).then(() => {
            if (this._moveInput.length() > 0) {
                this._lastDashDir = new Vec2(this._moveInput.x, this._moveInput.y).normalize();
            } else {
                this._lastDashDir = this.isFacingRight ? new Vec2(1, 0) : new Vec2(-1, 0);
            }

            this.isDashing = true;
            this.isJumping = false;
            this.isWallJumping = false;
            this._isJumpCut = false;

            this.lastPressedDashTime = 0;
            this.lastOnGroundTime = 0;

            this._dashesLeft--;
            this._isDashAttacking = true;

            this.setGravityScale(0, "dash");

            this.rb.linearVelocity = new Vec2(
                this.data.dashSpeed * this._lastDashDir.x,  // X方向速度 = dashSpeed * 方向X分量
                this.data.dashSpeed * this._lastDashDir.y   // Y方向速度 = dashSpeed * 方向Y分量
            );

            // Dash attack phase
            setTimeout(() => {
                this._isDashAttacking = false;
                this.setGravityScale(this.data.gravityScale);

                this.rb.linearVelocity = new Vec2(
                    this.data.dashEndSpeed.x * this._lastDashDir.x,
                    this.data.dashEndSpeed.y * this._lastDashDir.y
                );

                // Dash end phase
                setTimeout(() => {
                    this.isDashing = false;
                }, this.data.dashEndTime * 1000);
            }, this.data.dashAttackTime * 1000);
        });
    }


    private async sleep(duration: number): Promise<void> {
        return new Promise(resolve => {
            // game.setTimeScale(0);
            // this.tick = 0;
            director.getScheduler().setTimeScale(0);
            setTimeout(() => {
                // game.setTimeScale(1);
                // this.tick = 1;
                director.getScheduler().setTimeScale(1);
                resolve();
            }, duration * 1000);
        });
    }

    /** @zh 游戏速率 */
    private _tick: (dt: number) => void = director.tick;

    /** @zh 设置游戏速率 */
    public set tick(value: number) {
        director.tick = (dt: number) => {
            this._tick.call(director, dt * value);
        }
    }

    private refillDash(amount: number) {
        this._dashRefilling = true;
        setTimeout(() => {
            this._dashRefilling = false;
            this._dashesLeft = Math.min(this.data.dashAmount, this._dashesLeft + amount);
        }, this.data.dashRefillTime * 1000);
    }

    // Slide methods
    private checkSlideState() {
        if (this.canSlide() &&
            ((this.lastOnWallLeftTime > 0 && this._moveInput.x < 0) ||
            (this.lastOnWallRightTime > 0 && this._moveInput.x > 0))) {
            this.isSliding = true;
        } else {
            this.isSliding = false;
        }
    }

    private slide() {
        const speedDif = this.data.slideSpeed - this.rb.linearVelocity.y;
        let movement = speedDif * this.data.slideAccel;
        movement = math.clamp(
            movement,
            -Math.abs(speedDif) * (1 / PhysicsSystem2D.instance.fixedTimeStep),
            Math.abs(speedDif) * (1 / PhysicsSystem2D.instance.fixedTimeStep)
        );
        this.rb.applyForceToCenter(new Vec2(0, movement), true);
    }

    // Helper methods
    private applyGravity() {
        if (!this._isDashAttacking) {
            if (this.isSliding) {
                this.setGravityScale(0, "isSliding");
            } else if (this.rb.linearVelocity.y < 0 && this._moveInput.y < 0) {
                this.setGravityScale(this.data.gravityScale * this.data.fastFallGravityMult, "fastFallGravityMult");
                this.rb.linearVelocity = new Vec2(
                    this.rb.linearVelocity.x,
                    Math.max(this.rb.linearVelocity.y, -this.data.maxFastFallSpeed)
                );
            } else if (this._isJumpCut) {
                this.setGravityScale(this.data.gravityScale * this.data.jumpCutGravityMult, "jumpCutGravityMult");
                this.rb.linearVelocity = new Vec2(
                    this.rb.linearVelocity.x,
                    Math.max(this.rb.linearVelocity.y, -this.data.maxFallSpeed)
                );
            } else if ((this.isJumping || this.isWallJumping || this._isJumpFalling) &&
                Math.abs(this.rb.linearVelocity.y) < this.data.jumpHangTimeThreshold) {
                this.setGravityScale(this.data.gravityScale * this.data.jumpHangGravityMult, "jumpHangGravityMult");
            } else if (this.rb.linearVelocity.y < 0) {
                this.setGravityScale(this.data.gravityScale * this.data.fallGravityMult, "fallGravityMult");
                this.rb.linearVelocity = new Vec2(
                    this.rb.linearVelocity.x,
                    Math.max(this.rb.linearVelocity.y, -this.data.maxFallSpeed)
                );
            } else {
                this.setGravityScale(this.data.gravityScale, "else");
            }
        } else {
            this.setGravityScale(0, "isDashAttacking");
        }
    }

    public checkDirectionToFace(isMovingRight: boolean) {
        if (isMovingRight !== this.isFacingRight) {
            this.turn();
        }
    }

    public onJumpInput() {
        this.lastPressedJumpTime = this.data.jumpInputBufferTime;
    }

    public onJumpUpInput() {
        if (this.canJumpCut() || this.canWallJumpCut()) {
            this._isJumpCut = true;
        }
    }

    public onDashInput() {
        this.lastPressedDashTime = this.data.dashInputBufferTime;
    }

    public setGravityScale(scale: number, msg: string = "") {
        this.rb.gravityScale = scale;
        console.log(`GravityScale: ${msg})`);
    }

    private canJump(): boolean {
        return this.lastOnGroundTime > 0 && !this.isJumping;
    }

    private canWallJump(): boolean {
        return this.lastPressedJumpTime > 0 &&
            this.lastOnWallTime > 0 &&
            this.lastOnGroundTime <= 0 &&
            (!this.isWallJumping ||
                (this.lastOnWallRightTime > 0 && this._lastWallJumpDir === 1) ||
                (this.lastOnWallLeftTime > 0 && this._lastWallJumpDir === -1));
    }

    private canJumpCut(): boolean {
        return this.isJumping && this.rb.linearVelocity.y > 0;
    }

    private canWallJumpCut(): boolean {
        return this.isWallJumping && this.rb.linearVelocity.y > 0;
    }

    private canDash(): boolean {
        if (!this.isDashing && this._dashesLeft < this.data.dashAmount && this.lastOnGroundTime > 0 && !this._dashRefilling) {
            this.refillDash(1);
        }
        return this._dashesLeft > 0;
    }

    private canSlide(): boolean {
        return this.lastOnWallTime > 0 &&
            !this.isJumping &&
            !this.isWallJumping &&
            !this.isDashing &&
            this.lastOnGroundTime <= 0;
    }
}